package com.jse;

import java.beans.XMLDecoder;
import java.beans.XMLEncoder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.management.RuntimeErrorException;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * XML 的快捷帮助函数
 * 
 * @author zozoh(zozohtnt@gmail.com)
 */
public abstract class Xmls {

    /**
     * 帮你快速获得一个 DocumentBuilder，方便 XML 解析。
     * 
     * @return 一个 DocumentBuilder 对象
     * @throws ParserConfigurationException
     */
    public static DocumentBuilder xmls() throws ParserConfigurationException {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        String FEATURE = null;
        
        // This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
        // Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl

        FEATURE = "http://apache.org/xml/features/disallow-doctype-decl";
        dbf.setFeature(FEATURE, true);

        // If you can't completely disable DTDs, then at least do the following:
        // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities

        // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities

        // JDK7+ - http://xml.org/sax/features/external-general-entities 
        FEATURE = "http://xml.org/sax/features/external-general-entities";
        dbf.setFeature(FEATURE, false);

        // Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities

        // Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities

        // JDK7+ - http://xml.org/sax/features/external-parameter-entities 
        FEATURE = "http://xml.org/sax/features/external-parameter-entities";
        dbf.setFeature(FEATURE, false);

        // Disable external DTDs as well
        FEATURE = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
        dbf.setFeature(FEATURE, false);

        // and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
        dbf.setXIncludeAware(false);
        dbf.setExpandEntityReferences(false);
        return dbf.newDocumentBuilder();
    }
    
    public static Document xml(InputStream ins) {
        return xml(ins, null);
    }

    /**
     * 快捷的解析 XML 文件的帮助方法，它会主动关闭输入流
     * 
     * @param ins
     *            XML 文件输入流
     * @return Document 对象
     */
    public static Document xml(InputStream ins, Charset charset) {
        try {
            if (charset == null)
                charset = StandardCharsets.UTF_8;
            return xmls().parse(new InputSource(new InputStreamReader(ins, charset)));
        }
        catch (SAXException e) {
            throw new RuntimeException(e);
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
        catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        finally {
        	Io.close(ins);
        }
    }

    public static Document xml(File xmlFile) {
        return xml(xmlFile, null);
    }
    
    /**
     * 快捷的解析 XML 文件的帮助方法
     * 
     * @param xmlFile
     *            XML 文件
     * @return Document 对象
     */
    public static Document xml(File xmlFile, Charset charset) {
        InputStream ins = null;
        try {
            ins = new FileInputStream(xmlFile);
            return xml(ins, charset);
        }
        catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static String getText(Element ele) {
        StringBuilder sb = new StringBuilder();
        joinText(ele, sb);
        return sb.toString().trim();
    }

    public static void joinText(Element ele, StringBuilder sb) {
        if (null == ele)
            return;
        NodeList nl = ele.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node nd = nl.item(i);
            switch (nd.getNodeType()) {
            case Node.TEXT_NODE:
                sb.append(nd.getNodeValue());
                break;
            case Node.CDATA_SECTION_NODE:
                sb.append(nd.getNodeValue());
                break;
            case Node.ELEMENT_NODE:
                joinText((Element) nd, sb);
                break;
            default:
                break;
            }
        }
    }

    /**
     * 从一个 XML 元素开始，根据一条 XPath 获取一个元素
     * 
     * @param ele
     *            XML 元素
     * @param xpath
     *            要获取的元素的 XPath
     * @return 元素，null 表示不存在
     */
    public static Element getEle(Element ele, String xpath) {
        XPathFactory factory = XPathFactory.newInstance();
        XPath xp = factory.newXPath();
        try {
            XPathExpression expression = xp.compile(xpath);
            return (Element) expression.evaluate(ele, XPathConstants.NODE);
        }
        catch (XPathExpressionException e) {
            throw new RuntimeException(e);
        }

    }

    /**
     * 判断某个元素下是否有子元素
     * 
     * @param ele
     *            元素
     * @param regex
     *            子元素名称的正则表达式，如果为 null，则元素内如果有任意元素都会返回 false
     * @return 是否有子元素
     */
    public static boolean hasChild(Element ele, String regex) {
        NodeList nl = ele.getChildNodes();
        int len = nl.getLength();
        for (int i = 0; i < len; i++) {
            Node nd = nl.item(i);
            if (nd instanceof Element) {
                if (null == regex)
                    return false;
                if (Lang.match(regex, ((Element) nd).getTagName()))
                    return true;
            }
        }
        return false;
    }

    /**
     * 获取该 XML 元素内所有的属性的值，按照Map的形式返回
     * 
     * @param ele
     *            XML 元素
     * @return 所有属性的值
     */
    public static Map<String, String> getAttrs(Element ele) {
        NamedNodeMap nodeMap = ele.getAttributes();
        Map<String, String> attrs = new HashMap<String, String>(nodeMap.getLength());
        for (int i = 0; i < nodeMap.getLength(); i++) {
            attrs.put(nodeMap.item(i).getNodeName(), nodeMap.item(i).getNodeValue());
        }
        return attrs;
    }

    /**
     * 从 XML 元素中得到指定属性的值，如该指定属性不存在，则返回Null
     * 
     * @param ele
     *            XML 元素
     * @return 该指定属性的值
     */
    public static String getAttr(Element ele, String attrName) {
        Node node = ele.getAttributes().getNamedItem(attrName);
        return node != null ? node.getNodeValue() : null;
    }

    /**
     * 从一个 XML 元素开始，根据一条 XPath 获取一组元素
     * 
     * @param ele
     *            XML 元素
     * @param xpath
     *            要获取的元素的 XPath
     * @return 元素列表
     */
    public static List<Element> getEles(Element ele, String xpath) {
        XPathFactory factory = XPathFactory.newInstance();
        XPath xp = factory.newXPath();
        try {
            XPathExpression expression = xp.compile(xpath);
            NodeList nodes = (NodeList) expression.evaluate(ele, XPathConstants.NODESET);
            List<Element> list = new ArrayList<Element>();
            int len = nodes.getLength();
            for (int i = 0; i < len; i++) {
                Node node = nodes.item(i);
                if (node instanceof Element) {
                    list.add((Element)node);
                }
            }
            return list;
        }
        catch (XPathExpressionException e) {
            throw new RuntimeException(e);
        }
    }
    
    public static String HEAD = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
    
    public static class XmlParserOpts {
        private boolean lowerFirst;
        private boolean dupAsList;
        private List<String> alwaysAsList;
        private boolean keeyBlankNode;
        private boolean attrAsKeyValue;
        public XmlParserOpts() {
        }
        
        
        public XmlParserOpts(boolean lowerFirst, boolean dupAsList, List<String> alwaysAsList, boolean keeyBlankNode) {
            super();
            this.lowerFirst = lowerFirst;
            this.dupAsList = dupAsList;
            this.alwaysAsList = alwaysAsList;
            this.keeyBlankNode = keeyBlankNode;
        }


        public boolean isLowerFirst() {
            return lowerFirst;
        }
        public void setLowerFirst(boolean lowerFirst) {
            this.lowerFirst = lowerFirst;
        }
        public boolean isDupAsList() {
            return dupAsList;
        }
        public void setDupAsList(boolean dupAsList) {
            this.dupAsList = dupAsList;
        }
        public List<String> getAlwaysAsList() {
            return alwaysAsList;
        }
        public void setAlwaysAsList(List<String> alwaysAsList) {
            this.alwaysAsList = alwaysAsList;
        }
        public boolean isKeeyBlankNode() {
            return keeyBlankNode;
        }
        public void setKeeyBlankNode(boolean keeyBlankNode) {
            this.keeyBlankNode = keeyBlankNode;
        }


        public boolean isAttrAsKeyValue() {
            return attrAsKeyValue;
        }


        public void setAttrAsKeyValue(boolean attrAsKeyValue) {
            this.attrAsKeyValue = attrAsKeyValue;
        }
        
    }
    
	/**
	 * 转换对象为xml
	 * @param obj
	 * @return
	 */
	public static String toXml(Object obj) {
		if(obj instanceof List) {
			return toXml((List<?>)obj);
		}else if(obj instanceof Map) {
			return toXml((Map<?,?>)obj);
		}else if(obj instanceof File) {
			try {
				File f=(File) obj;
				String xml=new String(Files.readAllBytes(Paths.get(f.getPath())));
				return xml;
			} catch (IOException e) {
				return "";
			}
		}else {
			return obj.toString();
		}
	}
	/**
	 * 转换list对象 xml
	 * @param obj
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public static String toXml(List<?> obj) {
		StringBuffer xml=new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
		String root="xml";
		xml.append("<"+root+">\n");
		for (int i = 0; i < obj.size(); i++) {
			Object item=obj.get(i);
			if(item instanceof Map) {
				Map<String, Object> map=(Map<String, Object>) item;
				map.put("desc", "");
				map.put("root", "table");
				String str=toXml(map);
				xml.append(str).append("\n");
			}else {
				xml.append("<item"+i+">").append(obj.get(i)).append("</item"+i+">\n");
			}
		}

		xml.append("</"+root+">");
		return xml.toString();
	}
	/**
	 * 转换map对象 xml
	 * @param obj
	 * @return
	 */
	public static String toXml(Map<?, ?> obj) {
		String desc="<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
		if(obj.containsKey("desc")) {
			desc=obj.get("desc").toString();
			obj.remove("desc");
		}
		StringBuffer xml=new StringBuffer(desc);
		String root="xml";
		if(obj.containsKey("root")) {
			root=obj.get("root").toString();
			obj.remove("root");
		}
		xml.append("<"+root+">\n");
		obj.forEach((k,v)->{
			xml.append("<"+k+">").append(v).append("</"+k+">\n");
		});
		xml.append("</"+root+">");
		return xml.toString();
	}
	
	/**
     * 对XML值进行安全处理，包括&,>,<,",',©,®六项，以保证保存到XML中是正确的
     * 
     * @param value 原值
     * @return      安全处理后的值
     */
    public static String toXmlSave(String value)
    {
        value = value.replaceAll("&", "&amp;");
        //把&放在第一个处理，防止转换的&再被转换一次
        value = value.replaceAll(">", "&gt;");
        value = value.replaceAll("<", "&lt;");
        value = value.replaceAll("\"", "&quot;");
        value = value.replaceAll("\'", "&apos;");
        value = value.replaceAll("©", "&copy;");
        value = value.replaceAll("®", "&reg;");
        
        return value;
    }
    
    /**
     * 对XML值进行加载处理，安全处理的逆处理，包括&,>,<,",',©,®六项
     * @param value 原值
     * @return      安全处理后的值
     */
    public static String toXmlLoad(String value)
    {
        value = value.replaceAll("&amp;", "&");
        //把&放在第一个处理，防止有转换两次的&也支持
        value = value.replaceAll("&gt;", ">");
        value = value.replaceAll("&lt;", "<");
        value = value.replaceAll("&quot;", "\"");
        value = value.replaceAll("&apos;", "\'");
        value = value.replaceAll("&copy;", "©");
        value = value.replaceAll("&reg;", "®");
        return value;
    }
    
    /**
     * 获取XML中属性列表
     * 
     * @param xml   XML字符串
     * @param name  字段名称
     * @return      XML子段列表
     */
    public static List<String> getXmlFieldList(String xml, String name)
    {
        List<String> list = new ArrayList<String>();
        
        String lField = "<"+name+">";
        String rField = "</"+name+">";
        int rLen = rField.length();
        
        for(int iLeft=xml.indexOf(lField);iLeft!=-1;iLeft=xml.indexOf(lField))
        {
            int iRight = xml.indexOf(rField, iLeft);
            if (iRight == -1)
                break;
            
            list.add(xml.substring(iLeft, iRight+rLen));
            xml = xml.substring(iRight+rLen);
        }
        
        return list;
    }
    
    /**
     * 读取XML中字段的值，允许没有field时返回空字符串
     * 
     * @param xml XML字符串
     * @param field 字段名
     * @return 返回XML中字段中的值
     * @throws Exception 如果字段内容不符合要求,则抛出此异常
     */
    public static String getXmlField(String xml, String field)
    {
        if (xml.length() < field.length() * 2 + 5)
            return null;
        
        String lField = "<"+field+">";
        String rField = "</"+field+">";
        int iLeft = xml.indexOf(lField);
        int iRight = xml.indexOf(rField);
        if (iLeft == -1 && iRight == -1)
            return null;
        
        if ((iLeft == -1 && iRight != -1) || (iLeft != -1 && iRight == -1))
            return null;
        
        int valueLen = iRight - iLeft - lField.length();
        if (valueLen <= 0)
            return null;
        
        return xml.substring(iLeft + lField.length(), iRight);
    }
    
    /**
     * 检查XML是否包含在tag内，并截除tag，返回截除后的XML
     * 
     * @param xml XML字符串
     * @param tag 标记
     * @return 返回被截除后的XML
     * @throws Exception 如果XML不是tag开头和结尾,则抛出此异常
     */
    public static String chkXmlByTag(String xml, String tag) throws Exception
    {
        if (xml.length() < tag.length() * 2 + 5)
        {
            throw new Exception("xml tag #"+tag+"# format error");
        }
        
        String lField = "<"+tag+">";
        String rField = "</"+tag+">";
        String left = xml.substring(0, lField.length());
        String right = xml.substring(xml.length() - rField.length());
        if (!lField.equals(left) || !rField.equals(right))
        {
            throw new Exception("xml tag #"+tag+"# format error");
        }
        else
        {
            xml = xml.substring(lField.length());
            xml = xml.substring(0, xml.length() - rField.length());
            return xml;
        }
    }
    
    /**
     * 检查XML中字段field的值，并截除field放置到buf中，返回截除后的XML
     * 
     * @param xml XML字符串
     * @param field 字段名
     * @param value 把被截取的值放置到buf中
     * @param len 值允许的最大长度
     * @param isFixed 值长度是否定长
     * @return 返回被截除后的XML
     * @throws Exception 如果字段内容不符合要求,则抛出此异常
     */
    public static String chkXmlByField(String xml, String field, StringBuffer value, int len, boolean isFixed) throws Exception
    {
        if (xml.length() < field.length() * 2 + 5)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        String lField = "<"+field+">";
        String rField = "</"+field+">";
        int iLeft = xml.indexOf(lField);
        int iRight = xml.indexOf(rField);
        if (iLeft == -1 || iRight == -1)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        int valueLen = iRight - iLeft - lField.length();
        if ((isFixed && valueLen != len) ||//F 且不等
            (!isFixed && (valueLen > len || valueLen < 1)))//V 且 (大于len 或 < 1)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        value.append(xml.substring(iLeft + lField.length(), iRight));
        return xml.substring(iRight + rField.length());
    }
    

    /**
     * 检查XML中字段field的值，并截除field放置到buf中，返回截除后的XML,允许该字段为空
     * 
     * @param xml XML字符串
     * @param field 字段名
     * @param value 把被截取的值放置到buf中
     * @param len 值允许的最大长度
     * @param isFixed 值长度是否定长
     * @return 返回被截除后的XML
     * @throws Exception 如果字段内容不符合要求,则抛出此异常
     */
    public static String chkXmlByFieldAllowEmpty(String xml, String field, StringBuffer value, int len, boolean isFixed) throws Exception
    {
        String lField = "<"+field+">";
        String rField = "</"+field+">";
        int iLeft = xml.indexOf(lField);
        int iRight = xml.indexOf(rField);
        if (iLeft == -1 && iRight == -1)
            return xml;
        
        if ((iLeft == -1 && iRight != -1) || (iLeft != -1 && iRight == -1))
            throw new Exception("xml tag #"+field+"# format error");
        
        int valueLen = iRight - iLeft - lField.length();
        if (valueLen == 0)//为空则不判断isFixed,len
            return xml.substring(iRight + rField.length());
        
        if ((isFixed && valueLen != len) ||//F 且不等
            (!isFixed && (valueLen > len || valueLen < 1)))//V 且 (大于len 或 < 1)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        value.append(xml.substring(iLeft + lField.length(), iRight));
        return xml.substring(iRight + rField.length());
    }
    
    
    /**
     * 读取XML中字段的
     * 
     * @param xml XML字符串
     * @param field 字段名
     * @param len 值允许的最大长度
     * @param isFixed 值长度是否定长
     * @return 返回XML中字段中的值
     * @throws Exception 如果字段内容不符合要求,则抛出此异常
     */
    public static String getXmlField(String xml, String field, int len, boolean isFixed) throws Exception
    {
        if (xml.length() < field.length() * 2 + 5)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        String lField = "<"+field+">";
        String rField = "</"+field+">";
        int iLeft = xml.indexOf(lField);
        int iRight = xml.indexOf(rField);
        if (iLeft == -1 || iRight == -1)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        int valueLen = iRight - iLeft - lField.length();
        if ((isFixed && valueLen != len) ||//F 且不等
            (!isFixed && (valueLen > len || valueLen < 1)))//V 且 (大于len 或 < 1)
        {
            throw new Exception("xml tag #"+field+"# format error");
        }
        
        return xml.substring(iLeft + lField.length(), iRight);
    }
    
    /********************************************************/
    //以下解析XML属性方法
    /********************************************************/
    
    /**
     * 获取XML中属性字段
     * 
     * @param xml 指定的XML
     * @param name 指定的名称
     * @return 值
     * @throws Exception
     */
    public static String getString(String xml, String name) throws Exception
    {
        String lField = name + "=\"";
        String rField = "\"";
        int iLeft = xml.indexOf(lField);
        if (iLeft == -1)
            return null;
        
        int iRight = xml.indexOf(rField, iLeft+lField.length());
        if (iRight == -1)
            return null;
        
        return xml.substring(iLeft+lField.length(), iRight);
    }
    
    /**
     * 获取XML中属性字段
     * 
     * @param xml 指定的XML
     * @param name 指定的名称
     * @return 值
     * @throws Exception
     */
    public static int getInt(String xml, String name) throws Exception
    {
        String value = getString(xml, name);
        if (value == null)
            throw new Exception("未找到字段值");
        
        return Integer.parseInt(value);
    }
    
    /**
     * 获取XML中属性字段
     * 
     * @param xml 指定的XML
     * @param name 指定的名称
     * @return 值
     * @throws Exception
     */
    public static long getLong(String xml, String name) throws Exception
    {
        String value = getString(xml, name);
        if (value == null)
            throw new Exception("未找到字段值");
        
        return Long.parseLong(value);
    }
    

    /**
     * 获取XML中属性列表
     * 
     * @param xml   XML字符串
     * @param name  字段名称
     * @return      XML子段列表
     */
    public static List<String> getList(String xml, String name)
    {
        List<String> list = new ArrayList<String>();
        
        String lField = "<"+name+" ";
        String rField = "</"+name+">";
        String rField2 = "/>";
        
        for(int iLeft=xml.indexOf(lField);iLeft!=-1;iLeft=xml.indexOf(lField))
        {
            int len = rField.length();
            int iRight = xml.indexOf(rField, iLeft);
            if (iRight == -1)
            {
                iRight = xml.indexOf(rField2, iLeft);
                len = rField2.length();
            }
            
            if (iRight == -1)
                break;
            
            String str = xml.substring(iLeft, iRight+len);
            list.add(str);
            xml = xml.substring(iRight+len);
        }
        
        return list;
    }
    
    /**
	 * 在XML中无效的字符 正则
	 */
	public static final String INVALID_REGEX = "[\\x00-\\x08\\x0b-\\x0c\\x0e-\\x1f]";
	/**
	 * XML格式化输出默认缩进量
	 */
	public static final int INDENT_DEFAULT = 2;

	/**
	 * 默认的DocumentBuilderFactory实现
	 */
	private static String defaultDocumentBuilderFactory = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl";

	/**
	 * 是否打开命名空间支持
	 */
	private static boolean namespaceAware = true;

	/**
	 * 禁用默认的DocumentBuilderFactory，禁用后如果有第三方的实现（如oracle的xdb包中的xmlparse），将会自动加载实现。
	 */
	synchronized public static void disableDefaultDocumentBuilderFactory() {
		defaultDocumentBuilderFactory = null;
	}

	/**
	 * 设置是否打开命名空间支持，默认打开
	 *
	 * @param isNamespaceAware 是否命名空间支持
	 * @since 5.3.1
	 */
	synchronized public static void setNamespaceAware(boolean isNamespaceAware) {
		namespaceAware = isNamespaceAware;
	}

	// -------------------------------------------------------------------------------------- Read

	/**
	 * 读取解析XML文件
	 *
	 * @param file XML文件
	 * @return XML文档对象
	 */
	public static Document readXML(File file) {
		if (false == file.exists()) {
			throw new RuntimeException("File [{}] not a exist!"+file.getAbsolutePath());
		}
		if (false == file.isFile()) {
			throw new RuntimeException("[{}] not a file!"+ file.getAbsolutePath());
		}

		try {
			file = file.getCanonicalFile();
		} catch (IOException e) {
			// ignore
		}

		FileInputStream in = null;
		try {
			in = new FileInputStream(file);
			return readXML(in);
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} finally {
			Io.close(in);
		}
	}

	/**
	 * 读取解析XML文件<br>
	 * 如果给定内容以“&lt;”开头，表示这是一个XML内容，直接读取，否则按照路径处理<br>
	 * 路径可以为相对路径，也可以是绝对路径，相对路径相对于ClassPath
	 *
	 * @param pathOrContent 内容或路径
	 * @return XML文档对象
	 * @since 3.0.9
	 */
	public static Document readXML(String pathOrContent) {
		if (pathOrContent.startsWith("<")) {
			return parseXml(pathOrContent);
		}
		return readXML(new File(pathOrContent));
	}

	/**
	 * 读取解析XML文件<br>
	 * 编码在XML中定义
	 *
	 * @param inputStream XML流
	 * @return XML文档对象
	 * @throws RuntimeException IO异常或转换异常
	 * @since 3.0.9
	 */
	public static Document readXML(InputStream inputStream) throws RuntimeException {
		return readXML(new InputSource(inputStream));
	}

	/**
	 * 读取解析XML文件
	 *
	 * @param reader XML流
	 * @return XML文档对象
	 * @throws RuntimeException IO异常或转换异常
	 * @since 3.0.9
	 */
	public static Document readXML(Reader reader) throws RuntimeException {
		return readXML(new InputSource(reader));
	}

	/**
	 * 读取解析XML文件<br>
	 * 编码在XML中定义
	 *
	 * @param source {@link InputSource}
	 * @return XML文档对象
	 * @since 3.0.9
	 */
	public static Document readXML(InputSource source) {
		final DocumentBuilder builder = createDocumentBuilder();
		try {
			return builder.parse(source);
		} catch (Exception e) {
			throw new RuntimeException("Parse XML from stream error!"+e);
		}
	}

	/**
	 * 将String类型的XML转换为XML文档
	 *
	 * @param xmlStr XML字符串
	 * @return XML文档
	 */
	public static Document parseXml(String xmlStr) {
		if (Strings.isBlank(xmlStr)) {
			throw new IllegalArgumentException("XML content string is empty !");
		}
		xmlStr = cleanInvalid(xmlStr);
		return readXML(new StringReader(xmlStr));
	}

	/**
	 * 从XML中读取对象 Reads serialized object from the XML file.
	 *
	 * @param <T>    对象类型
	 * @param source XML文件
	 * @return 对象
	 */
	public static <T> T readObjectFromXml(File source) {
		try {
			return readObjectFromXml(new InputSource(new FileInputStream(source)));
		} catch (FileNotFoundException e) {
			throw new RuntimeException(e);
		} 
	}

	/**
	 * 从XML中读取对象 Reads serialized object from the XML file.
	 *
	 * @param <T>    对象类型
	 * @param xmlStr XML内容
	 * @return 对象
	 * @since 3.2.0
	 */
	public static <T> T readObjectFromXml(String xmlStr) {
		return readObjectFromXml(new InputSource(new StringReader(xmlStr)));
	}

	/**
	 * 从XML中读取对象 Reads serialized object from the XML file.
	 *
	 * @param <T>    对象类型
	 * @param source {@link InputSource}
	 * @return 对象
	 * @since 3.2.0
	 */
	@SuppressWarnings("unchecked")
	public static <T> T readObjectFromXml(InputSource source) {
		Object result;
		XMLDecoder xmldec = null;
		try {
			xmldec = new XMLDecoder(source);
			result = xmldec.readObject();
		} finally {
			Io.close(xmldec);
		}
		return (T) result;
	}

	// -------------------------------------------------------------------------------------- Write

	/**
	 * 将XML文档转换为String<br>
	 * 字符编码使用XML文档中的编码，获取不到则使用UTF-8<br>
	 * 默认非格式化输出，若想格式化请使用{@link #format(Document)}
	 *
	 * @param doc XML文档
	 * @return XML字符串
	 */
	public static String toStr(Document doc) {
		return toStr(doc, false);
	}

	/**
	 * 将XML文档转换为String<br>
	 * 字符编码使用XML文档中的编码，获取不到则使用UTF-8
	 *
	 * @param doc      XML文档
	 * @param isPretty 是否格式化输出
	 * @return XML字符串
	 * @since 3.0.9
	 */
	public static String toStr(Document doc, boolean isPretty) {
		return toStr(doc,"UTF-8", isPretty);
	}

	/**
	 * 将XML文档转换为String<br>
	 * 字符编码使用XML文档中的编码，获取不到则使用UTF-8
	 *
	 * @param doc      XML文档
	 * @param charset  编码
	 * @param isPretty 是否格式化输出
	 * @return XML字符串
	 * @since 3.0.9
	 */
	public static String toStr(Document doc, String charset, boolean isPretty) {
		return toStr(doc, charset, isPretty, false);
	}

	/**
	 * 将XML文档转换为String<br>
	 * 字符编码使用XML文档中的编码，获取不到则使用UTF-8
	 *
	 * @param doc                XML文档
	 * @param charset            编码
	 * @param isPretty           是否格式化输出
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @return XML字符串
	 * @since 5.1.2
	 */
	public static String toStr(Document doc, String charset, boolean isPretty, boolean omitXmlDeclaration) {
		final StringWriter writer = new StringWriter();
		try {
			write(doc, writer, charset, isPretty ? INDENT_DEFAULT : 0, omitXmlDeclaration);
		} catch (Exception e) {
			throw new RuntimeException("Trans xml document to string error!"+e);
		}
		return writer.toString();
	}

	/**
	 * 格式化XML输出
	 *
	 * @param doc {@link Document} XML文档
	 * @return 格式化后的XML字符串
	 * @since 4.4.5
	 */
	public static String format(Document doc) {
		return toStr(doc, true);
	}

	/**
	 * 格式化XML输出
	 *
	 * @param xmlStr XML字符串
	 * @return 格式化后的XML字符串
	 * @since 4.4.5
	 */
	public static String format(String xmlStr) {
		return format(parseXml(xmlStr));
	}

	/**
	 * 将XML文档写入到文件<br>
	 * 使用Document中的编码
	 *
	 * @param doc          XML文档
	 * @param absolutePath 文件绝对路径，不存在会自动创建
	 */
	public static void toFile(Document doc, String absolutePath) {
		toFile(doc, absolutePath, null);
	}

	/**
	 * 将XML文档写入到文件<br>
	 *
	 * @param doc     XML文档
	 * @param path    文件路径绝对路径或相对ClassPath路径，不存在会自动创建
	 * @param charset 自定义XML文件的编码，如果为{@code null} 读取XML文档中的编码，否则默认UTF-8
	 */
	public static void toFile(Document doc, String path, String charset) {
		if (Strings.isBlank(charset)) {
			charset = doc.getXmlEncoding();
		}
		if (Strings.isBlank(charset)) {
			charset = "UTF-8";
		}

		FileWriter writer = null;
		try {
			writer = new FileWriter(path);
			write(doc, writer, charset, INDENT_DEFAULT);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} finally {
			Io.close(writer);
		}
	}

	/**
	 * 将XML文档写出
	 *
	 * @param node    {@link Node} XML文档节点或文档本身
	 * @param writer  写出的Writer，Writer决定了输出XML的编码
	 * @param charset 编码
	 * @param indent  格式化输出中缩进量，小于1表示不格式化输出
	 * @since 3.0.9
	 */
	public static void write(Node node, Writer writer, String charset, int indent) {
		transform(new DOMSource(node), new StreamResult(writer), charset, indent);
	}

	/**
	 * 将XML文档写出
	 *
	 * @param node               {@link Node} XML文档节点或文档本身
	 * @param writer             写出的Writer，Writer决定了输出XML的编码
	 * @param charset            编码
	 * @param indent             格式化输出中缩进量，小于1表示不格式化输出
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @since 5.1.2
	 */
	public static void write(Node node, Writer writer, String charset, int indent, boolean omitXmlDeclaration) {
		transform(new DOMSource(node), new StreamResult(writer), charset, indent, omitXmlDeclaration);
	}

	/**
	 * 将XML文档写出
	 *
	 * @param node    {@link Node} XML文档节点或文档本身
	 * @param out     写出的Writer，Writer决定了输出XML的编码
	 * @param charset 编码
	 * @param indent  格式化输出中缩进量，小于1表示不格式化输出
	 * @since 4.0.8
	 */
	public static void write(Node node, OutputStream out, String charset, int indent) {
		transform(new DOMSource(node), new StreamResult(out), charset, indent);
	}

	/**
	 * 将XML文档写出
	 *
	 * @param node               {@link Node} XML文档节点或文档本身
	 * @param out                写出的Writer，Writer决定了输出XML的编码
	 * @param charset            编码
	 * @param indent             格式化输出中缩进量，小于1表示不格式化输出
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @since 5.1.2
	 */
	public static void write(Node node, OutputStream out, String charset, int indent, boolean omitXmlDeclaration) {
		transform(new DOMSource(node), new StreamResult(out), charset, indent, omitXmlDeclaration);
	}

	/**
	 * 将XML文档写出<br>
	 * 格式化输出逻辑参考：https://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java
	 *
	 * @param source  源
	 * @param result  目标
	 * @param charset 编码
	 * @param indent  格式化输出中缩进量，小于1表示不格式化输出
	 * @since 4.0.9
	 */
	public static void transform(Source source, Result result, String charset, int indent) {
		transform(source, result, charset, indent, false);
	}

	/**
	 * 将XML文档写出<br>
	 * 格式化输出逻辑参考：https://stackoverflow.com/questions/139076/how-to-pretty-print-xml-from-java
	 *
	 * @param source             源
	 * @param result             目标
	 * @param charset            编码
	 * @param indent             格式化输出中缩进量，小于1表示不格式化输出
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @since 5.1.2
	 */
	public static void transform(Source source, Result result, String charset, int indent, boolean omitXmlDeclaration) {
		final TransformerFactory factory = TransformerFactory.newInstance();
		try {
			final Transformer xformer = factory.newTransformer();
			if (indent > 0) {
				xformer.setOutputProperty(OutputKeys.INDENT, "yes");
				xformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", String.valueOf(indent));
			}
			if (!Strings.isBlank(charset)) {
				xformer.setOutputProperty(OutputKeys.ENCODING, charset);
			}
			if (omitXmlDeclaration) {
				xformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
			}
			xformer.transform(source, result);
		} catch (Exception e) {
			throw new RuntimeException("Trans xml document to string error!"+e);
		}
	}

	// -------------------------------------------------------------------------------------- Create

	/**
	 * 创建XML文档<br>
	 * 创建的XML默认是utf8编码，修改编码的过程是在toStr和toFile方法里，即XML在转为文本的时候才定义编码
	 *
	 * @return XML文档
	 * @since 4.0.8
	 */
	public static Document createXml() {
		return createDocumentBuilder().newDocument();
	}

	/**
	 * 创建 DocumentBuilder
	 *
	 * @return DocumentBuilder
	 * @since 4.1.2
	 */
	public static DocumentBuilder createDocumentBuilder() {
		DocumentBuilder builder;
		try {
			builder = createDocumentBuilderFactory().newDocumentBuilder();
		} catch (Exception e) {
			throw new RuntimeException("Create xml document error!"+e);
		}
		return builder;
	}

	/**
	 * 创建{@link DocumentBuilderFactory}
	 * <p>
	 * 默认使用"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"<br>
	 * 如果使用第三方实现，请调用{@link #disableDefaultDocumentBuilderFactory()}
	 * </p>
	 *
	 * @return {@link DocumentBuilderFactory}
	 */
	public static DocumentBuilderFactory createDocumentBuilderFactory() {
		final DocumentBuilderFactory factory;
		if (Strings.isNotEmpty(defaultDocumentBuilderFactory)) {
			factory = DocumentBuilderFactory.newInstance(defaultDocumentBuilderFactory, null);
		} else {
			factory = DocumentBuilderFactory.newInstance();
		}
		// 默认打开NamespaceAware，getElementsByTagNameNS可以使用命名空间
		factory.setNamespaceAware(namespaceAware);
		return disableXXE(factory);
	}

	/**
	 * 创建XML文档<br>
	 * 创建的XML默认是utf8编码，修改编码的过程是在toStr和toFile方法里，即XML在转为文本的时候才定义编码
	 *
	 * @param rootElementName 根节点名称
	 * @return XML文档
	 */
	public static Document createXml(String rootElementName) {
		return createXml(rootElementName, null);
	}

	/**
	 * 创建XML文档<br>
	 * 创建的XML默认是utf8编码，修改编码的过程是在toStr和toFile方法里，即XML在转为文本的时候才定义编码
	 *
	 * @param rootElementName 根节点名称
	 * @param namespace       命名空间，无则传null
	 * @return XML文档
	 * @since 5.0.4
	 */
	public static Document createXml(String rootElementName, String namespace) {
		final Document doc = createXml();
		doc.appendChild(null == namespace ? doc.createElement(rootElementName) : doc.createElementNS(namespace, rootElementName));
		return doc;
	}

	// -------------------------------------------------------------------------------------- Function

	/**
	 * 获得XML文档根节点
	 *
	 * @param doc {@link Document}
	 * @return 根节点
	 * @see Document#getDocumentElement()
	 * @since 3.0.8
	 */
	public static Element getRootElement(Document doc) {
		return (null == doc) ? null : doc.getDocumentElement();
	}

	/**
	 * 获取节点所在的Document
	 *
	 * @param node 节点
	 * @return {@link Document}
	 * @since 5.3.0
	 */
	public static Document getOwnerDocument(Node node) {
		return (node instanceof Document) ? (Document) node : node.getOwnerDocument();
	}

	/**
	 * 去除XML文本中的无效字符
	 *
	 * @param xmlContent XML文本
	 * @return 当传入为null时返回null
	 */
	public static String cleanInvalid(String xmlContent) {
		if (xmlContent == null) {
			return null;
		}
		return xmlContent.replaceAll(INVALID_REGEX, "");
	}

	/**
	 * 根据节点名获得子节点列表
	 *
	 * @param element 节点
	 * @param tagName 节点名，如果节点名为空（null或blank），返回所有子节点
	 * @return 节点列表
	 */
	public static List<Element> getElements(Element element, String tagName) {
		final NodeList nodeList = Strings.isBlank(tagName) ? element.getChildNodes() : element.getElementsByTagName(tagName);
		return transElements(element, nodeList);
	}

	/**
	 * 根据节点名获得第一个子节点
	 *
	 * @param element 节点
	 * @param tagName 节点名
	 * @return 节点
	 */
	public static Element getElement(Element element, String tagName) {
		final NodeList nodeList = element.getElementsByTagName(tagName);
		if (nodeList == null || nodeList.getLength() < 1) {
			return null;
		}
		int length = nodeList.getLength();
		for (int i = 0; i < length; i++) {
			Element childEle = (Element) nodeList.item(i);
			if (childEle == null || childEle.getParentNode() == element) {
				return childEle;
			}
		}
		return null;
	}

	/**
	 * 根据节点名获得第一个子节点
	 *
	 * @param element 节点
	 * @param tagName 节点名
	 * @return 节点中的值
	 */
	public static String elementText(Element element, String tagName) {
		Element child = getElement(element, tagName);
		return child == null ? null : child.getTextContent();
	}

	/**
	 * 根据节点名获得第一个子节点
	 *
	 * @param element      节点
	 * @param tagName      节点名
	 * @param defaultValue 默认值
	 * @return 节点中的值
	 */
	public static String elementText(Element element, String tagName, String defaultValue) {
		Element child = getElement(element, tagName);
		return child == null ? defaultValue : child.getTextContent();
	}

	/**
	 * 将NodeList转换为Element列表
	 *
	 * @param nodeList NodeList
	 * @return Element列表
	 */
	public static List<Element> transElements(NodeList nodeList) {
		return transElements(null, nodeList);
	}

	/**
	 * 将NodeList转换为Element列表<br>
	 * 非Element节点将被忽略
	 *
	 * @param parentEle 父节点，如果指定将返回此节点的所有直接子节点，null返回所有就节点
	 * @param nodeList  NodeList
	 * @return Element列表
	 */
	public static List<Element> transElements(Element parentEle, NodeList nodeList) {
		int length = nodeList.getLength();
		final ArrayList<Element> elements = new ArrayList<>(length);
		Node node;
		Element element;
		for (int i = 0; i < length; i++) {
			node = nodeList.item(i);
			if (Node.ELEMENT_NODE == node.getNodeType()) {
				element = (Element) nodeList.item(i);
				if (parentEle == null || element.getParentNode() == parentEle) {
					elements.add(element);
				}
			}
		}

		return elements;
	}

	/**
	 * 将可序列化的对象转换为XML写入文件，已经存在的文件将被覆盖<br>
	 * Writes serializable object to a XML file. Existing file will be overwritten
	 *
	 * @param dest 目标文件
	 * @param bean 对象
	 */
	public static void writeObjectAsXml(File dest, Object bean) {
		XMLEncoder xmlenc = null;
		try {
			xmlenc = new XMLEncoder(new FileOutputStream(dest));
			xmlenc.writeObject(bean);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} finally {
			// 关闭XMLEncoder会相应关闭OutputStream
			Io.close(xmlenc);
		}
	}

	/**
	 * 创建XPath<br>
	 * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
	 *
	 * @return {@link XPath}
	 * @since 3.2.0
	 */
	public static XPath createXPath() {
		return XPathFactory.newInstance().newXPath();
	}

	/**
	 * 通过XPath方式读取XML节点等信息<br>
	 * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
	 *
	 * @param expression XPath表达式
	 * @param source     资源，可以是Docunent、Node节点等
	 * @return 匹配返回类型的值
	 * @since 4.0.9
	 */
	public static Element getElementByXPath(String expression, Object source) {
		return (Element) getNodeByXPath(expression, source);
	}

	/**
	 * 通过XPath方式读取XML的NodeList<br>
	 * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
	 *
	 * @param expression XPath表达式
	 * @param source     资源，可以是Docunent、Node节点等
	 * @return NodeList
	 * @since 4.0.9
	 */
	public static NodeList getNodeListByXPath(String expression, Object source) {
		return (NodeList) getByXPath(expression, source, XPathConstants.NODESET);
	}

	/**
	 * 通过XPath方式读取XML节点等信息<br>
	 * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
	 *
	 * @param expression XPath表达式
	 * @param source     资源，可以是Docunent、Node节点等
	 * @return 匹配返回类型的值
	 * @since 4.0.9
	 */
	public static Node getNodeByXPath(String expression, Object source) {
		return (Node) getByXPath(expression, source, XPathConstants.NODE);
	}

	/**
	 * 通过XPath方式读取XML节点等信息<br>
	 * Xpath相关文章：https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html
	 *
	 * @param expression XPath表达式
	 * @param source     资源，可以是Docunent、Node节点等
	 * @param returnType 返回类型，{@link javax.xml.xpath.XPathConstants}
	 * @return 匹配返回类型的值
	 * @since 3.2.0
	 */
	public static Object getByXPath(String expression, Object source, QName returnType) {
		NamespaceContext nsContext = null;
		if (source instanceof Node) {
			nsContext = new UniversalNamespaceCache((Node)source, false);
		}
		return getByXPath(expression, source, returnType, nsContext);
	}

	/**
	 * 通过XPath方式读取XML节点等信息<br>
	 * Xpath相关文章：<br>
	 * https://www.ibm.com/developerworks/cn/xml/x-javaxpathapi.html<br>
	 * https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/
	 *
	 * @param expression XPath表达式
	 * @param source     资源，可以是Docunent、Node节点等
	 * @param returnType 返回类型，{@link javax.xml.xpath.XPathConstants}
	 * @param nsContext  {@link NamespaceContext}
	 * @return 匹配返回类型的值
	 * @since 5.3.1
	 */
	public static Object getByXPath(String expression, Object source, QName returnType, NamespaceContext nsContext) {
		final XPath xPath = createXPath();
		if (null != nsContext) {
			xPath.setNamespaceContext(nsContext);
		}
		try {
			if (source instanceof InputSource) {
				return xPath.evaluate(expression, (InputSource) source, returnType);
			} else {
				return xPath.evaluate(expression, source, returnType);
			}
		} catch (XPathExpressionException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * 转义XML特殊字符:
	 *
	 * <pre>
	 * &amp; (ampersand) 替换为 &amp;amp;
	 * &lt; (小于) 替换为 &amp;lt;
	 * &gt; (大于) 替换为 &amp;gt;
	 * &quot; (双引号) 替换为 &amp;quot;
	 * </pre>
	 *
	 * @param string 被替换的字符串
	 * @return 替换后的字符串
	 * @since 4.0.8
	 */
//	public static String escape(String string) {
//		return Strings.escape(string);
//	}

	/**
	 * 反转义XML特殊字符:
	 *
	 * @param string 被替换的字符串
	 * @return 替换后的字符串
	 * @see EscapeUtil#unescape(String)
	 * @since 5.0.6
	 */
//	public static String unescape(String string) {
//		return Strings.unescape(string);
//	}

	/**
	 * XML格式字符串转换为Map
	 *
	 * @param xmlStr XML字符串
	 * @return XML数据转换后的Map
	 * @since 4.0.8
	 */
	public static Map<String, Object> xmlToMap(String xmlStr) {
		return xmlToMap(xmlStr, new HashMap<>());
	}

	/**
	 * XML转Java Bean
	 *
	 * @param <T>  bean类型
	 * @param node XML节点
	 * @param bean bean类
	 * @return bean
	 * @since 5.2.4
	 */
	public static <T> T xmlToBean(Node node, Class<T> bean) {
		return Lang.mapToBean(xmlToMap(node), bean);
	}

	/**
	 * XML格式字符串转换为Map
	 *
	 * @param node XML节点
	 * @return XML数据转换后的Map
	 * @since 4.0.8
	 */
	public static Map<String, Object> xmlToMap(Node node) {
		return xmlToMap(node, new HashMap<>());
	}

	/**
	 * XML格式字符串转换为Map<br>
	 * 只支持第一级别的XML，不支持多级XML
	 *
	 * @param xmlStr XML字符串
	 * @param result 结果Map类型
	 * @return XML数据转换后的Map
	 * @since 4.0.8
	 */
	public static Map<String, Object> xmlToMap(String xmlStr, Map<String, Object> result) {
		final Document doc = parseXml(xmlStr);
		final Element root = getRootElement(doc);
		root.normalize();

		return xmlToMap(root, result);
	}

	/**
	 * XML节点转换为Map
	 *
	 * @param node   XML节点
	 * @param result 结果Map类型
	 * @return XML数据转换后的Map
	 * @since 4.0.8
	 */
	@SuppressWarnings("unchecked")
	public static Map<String, Object> xmlToMap(Node node, Map<String, Object> result) {
		if (null == result) {
			result = new HashMap<>();
		}
		final NodeList nodeList = node.getChildNodes();
		final int length = nodeList.getLength();
		Node childNode;
		Element childEle;
		for (int i = 0; i < length; ++i) {
			childNode = nodeList.item(i);
			if (false == isElement(childNode)) {
				continue;
			}

			childEle = (Element) childNode;
			final Object value = result.get(childEle.getNodeName());
			Object newValue;
			if (childEle.hasChildNodes()) {
				// 子节点继续递归遍历
				final Map<String, Object> map = xmlToMap(childEle);
				if (!Lang.isEmpty(map)) {
					newValue = map;
				} else {
					newValue = childEle.getTextContent();
				}
			} else {
				newValue = childEle.getTextContent();
			}


			if (null != newValue) {
				if (null != value) {
					if (value instanceof List) {
						((List<Object>) value).add(newValue);
					} else {
						result.put(childEle.getNodeName(), List.of(value, newValue));
					}
				} else {
					result.put(childEle.getNodeName(), newValue);
				}
			}
		}
		return result;
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data Map类型数据
	 * @return XML格式的字符串
	 * @since 5.1.2
	 */
	public static String mapToXmlStr(Map<?, ?> data) {
		return toStr(mapToXml(data, "xml"));
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data               Map类型数据
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @return XML格式的字符串
	 * @since 5.1.2
	 */
	public static String mapToXmlStr(Map<?, ?> data, boolean omitXmlDeclaration) {
		return toStr(mapToXml(data, "xml"), "UTF-8", false, omitXmlDeclaration);
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data     Map类型数据
	 * @param rootName 根节点名
	 * @return XML格式的字符串
	 * @since 4.0.8
	 */
	public static String mapToXmlStr(Map<?, ?> data, String rootName) {
		return toStr(mapToXml(data, rootName));
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data      Map类型数据
	 * @param rootName  根节点名
	 * @param namespace 命名空间，可以为null
	 * @return XML格式的字符串
	 * @since 5.0.4
	 */
	public static String mapToXmlStr(Map<?, ?> data, String rootName, String namespace) {
		return toStr(mapToXml(data, rootName, namespace));
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data               Map类型数据
	 * @param rootName           根节点名
	 * @param namespace          命名空间，可以为null
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @return XML格式的字符串
	 * @since 5.1.2
	 */
	public static String mapToXmlStr(Map<?, ?> data, String rootName, String namespace, boolean omitXmlDeclaration) {
		return toStr(mapToXml(data, rootName, namespace), "UTF-8", false, omitXmlDeclaration);
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data               Map类型数据
	 * @param rootName           根节点名
	 * @param namespace          命名空间，可以为null
	 * @param isPretty           是否格式化输出
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @return XML格式的字符串
	 * @since 5.1.2
	 */
	public static String mapToXmlStr(Map<?, ?> data, String rootName, String namespace, boolean isPretty, boolean omitXmlDeclaration) {
		return toStr(mapToXml(data, rootName, namespace),"UTF-8", isPretty);
	}

	/**
	 * 将Map转换为XML格式的字符串
	 *
	 * @param data               Map类型数据
	 * @param rootName           根节点名
	 * @param namespace          命名空间，可以为null
	 * @param charset            编码
	 * @param isPretty           是否格式化输出
	 * @param omitXmlDeclaration 是否输出 xml Declaration
	 * @return XML格式的字符串
	 * @since 5.1.2
	 */
	public static String mapToXmlStr(Map<?, ?> data, String rootName, String namespace, String charset, boolean isPretty, boolean omitXmlDeclaration) {
		return toStr(mapToXml(data, rootName, namespace), charset, isPretty, omitXmlDeclaration);
	}

	/**
	 * 将Map转换为XML
	 *
	 * @param data     Map类型数据
	 * @param rootName 根节点名
	 * @return XML
	 * @since 4.0.9
	 */
	public static Document mapToXml(Map<?, ?> data, String rootName) {
		return mapToXml(data, rootName, null);
	}

	/**
	 * 将Map转换为XML
	 *
	 * @param data      Map类型数据
	 * @param rootName  根节点名
	 * @param namespace 命名空间，可以为null
	 * @return XML
	 * @since 5.0.4
	 */
	public static Document mapToXml(Map<?, ?> data, String rootName, String namespace) {
		final Document doc = createXml();
		final Element root = appendChild(doc, rootName, namespace);

		appendMap(doc, root, data);
		return doc;
	}

	/**
	 * 将Bean转换为XML
	 *
	 * @param bean      Bean对象
	 * @param namespace 命名空间，可以为null
	 * @return XML
	 * @since 5.2.4
	 */
	public static Document beanToXml(Object bean, String namespace) {
		if (null == bean) {
			return null;
		}
		return mapToXml(Lang.beanToMap(bean), bean.getClass().getSimpleName(), namespace);
	}

	/**
	 * 给定节点是否为{@link Element} 类型节点
	 *
	 * @param node 节点
	 * @return 是否为{@link Element} 类型节点
	 * @since 4.0.8
	 */
	public static boolean isElement(Node node) {
		return (null != node) && Node.ELEMENT_NODE == node.getNodeType();
	}

	/**
	 * 在已有节点上创建子节点
	 *
	 * @param node    节点
	 * @param tagName 标签名
	 * @return 子节点
	 * @since 4.0.9
	 */
	public static Element appendChild(Node node, String tagName) {
		return appendChild(node, tagName, null);
	}

	/**
	 * 在已有节点上创建子节点
	 *
	 * @param node      节点
	 * @param tagName   标签名
	 * @param namespace 命名空间，无传null
	 * @return 子节点
	 * @since 5.0.4
	 */
	public static Element appendChild(Node node, String tagName, String namespace) {
		final Document doc = getOwnerDocument(node);
		final Element child = (null == namespace) ? doc.createElement(tagName) : doc.createElementNS(namespace, tagName);
		node.appendChild(child);
		return child;
	}

	/**
	 * 创建文本子节点
	 *
	 * @param node 节点
	 * @param text 文本
	 * @return 子节点
	 * @since 5.3.0
	 */
	public static Node appendText(Node node, CharSequence text) {
		return appendText(getOwnerDocument(node), node, text);
	}
	// ---------------------------------------------------------------------------------------- Private method start

	/**
	 * 追加数据子节点，可以是Map、集合、文本
	 *
	 * @param doc  {@link Document}
	 * @param node 节点
	 * @param data 数据
	 */
	@SuppressWarnings("rawtypes")
	private static void append(Document doc, Node node, Object data) {
		if (data instanceof Map) {
			// 如果值依旧为map，递归继续
			appendMap(doc, node, (Map) data);
		} else if (data instanceof Iterator) {
			// 如果值依旧为map，递归继续
			appendIterator(doc, node, (Iterator) data);
		} else if (data instanceof Iterable) {
			// 如果值依旧为map，递归继续
			appendIterator(doc, node, ((Iterable) data).iterator());
		} else {
			appendText(doc, node, data.toString());
		}
	}

	/**
	 * 追加Map数据子节点
	 *
	 * @param doc  {@link Document}
	 * @param node 当前节点
	 * @param data Map类型数据
	 * @since 4.0.8
	 */
	@SuppressWarnings({"rawtypes", "unchecked"})
	private static void appendMap(Document doc, Node node, Map data) {
		data.forEach((key, value) -> {
			if (null != key) {
				final Element child = appendChild(node, key.toString());
				if (null != value) {
					append(doc, child, value);
				}
			}
		});
	}

	/**
	 * 追加集合节点
	 *
	 * @param doc  {@link Document}
	 * @param node 节点
	 * @param data 数据
	 */
	@SuppressWarnings("rawtypes")
	private static void appendIterator(Document doc, Node node, Iterator data) {
		final Node parentNode = node.getParentNode();
		boolean isFirst = true;
		Object eleData;
		while (data.hasNext()) {
			eleData = data.next();
			if (isFirst) {
				append(doc, node, eleData);
				isFirst = false;
			} else {
				final Node cloneNode = node.cloneNode(false);
				parentNode.appendChild(cloneNode);
				append(doc, cloneNode, eleData);
			}
		}
	}

	/**
	 * 追加文本节点
	 *
	 * @param doc  {@link Document}
	 * @param node 节点
	 * @param text 文本内容
	 * @return 增加的子节点，即Text节点
	 * @since 5.3.0
	 */
	private static Node appendText(Document doc, Node node, CharSequence text) {
		return node.appendChild(doc.createTextNode(Strings.def(text,"").toString()));
	}

	/**
	 * 关闭XXE，避免漏洞攻击<br>
	 * see: https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#JAXP_DocumentBuilderFactory.2C_SAXParserFactory_and_DOM4J
	 *
	 * @param dbf DocumentBuilderFactory
	 * @return DocumentBuilderFactory
	 */
	private static DocumentBuilderFactory disableXXE(DocumentBuilderFactory dbf) {
		String feature;
		try {
			// This is the PRIMARY defense. If DTDs (doctypes) are disallowed, almost all XML entity attacks are prevented
			// Xerces 2 only - http://xerces.apache.org/xerces2-j/features.html#disallow-doctype-decl
			feature = "http://apache.org/xml/features/disallow-doctype-decl";
			dbf.setFeature(feature, true);
			// If you can't completely disable DTDs, then at least do the following:
			// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-general-entities
			// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-general-entities
			// JDK7+ - http://xml.org/sax/features/external-general-entities
			feature = "http://xml.org/sax/features/external-general-entities";
			dbf.setFeature(feature, false);
			// Xerces 1 - http://xerces.apache.org/xerces-j/features.html#external-parameter-entities
			// Xerces 2 - http://xerces.apache.org/xerces2-j/features.html#external-parameter-entities
			// JDK7+ - http://xml.org/sax/features/external-parameter-entities
			feature = "http://xml.org/sax/features/external-parameter-entities";
			dbf.setFeature(feature, false);
			// Disable external DTDs as well
			feature = "http://apache.org/xml/features/nonvalidating/load-external-dtd";
			dbf.setFeature(feature, false);
			// and these as well, per Timothy Morgan's 2014 paper: "XML Schema, DTD, and Entity Attacks"
			dbf.setXIncludeAware(false);
			dbf.setExpandEntityReferences(false);
		} catch (ParserConfigurationException e) {
			// ignore
		}
		return dbf;
	}

	/**
	 * 全局命名空间上下文<br>
	 * 见：https://www.ibm.com/developerworks/cn/xml/x-nmspccontext/
	 */
	public static class UniversalNamespaceCache implements NamespaceContext {
		private static final String DEFAULT_NS = "DEFAULT";
		private Map<String, String> prefix2Uri = new HashMap<String, String>();
		private Map<String, String> uri2Prefix = new HashMap<String, String>();

		/**
		 * This constructor parses the document and stores all namespaces it can
		 * find. If toplevelOnly is true, only namespaces in the root are used.
		 * 
		 * @param document
		 *            source document
		 * @param toplevelOnly
		 *            restriction of the search to enhance performance
		 */
		public UniversalNamespaceCache(Node document,boolean toplevelOnly) {
			examineNode(document.getFirstChild(), toplevelOnly);
			for (String key : prefix2Uri.keySet()) {
				System.out
						.println("prefix " + key + ": uri " + prefix2Uri.get(key));
			}
		}

		/**
		 * A single node is read, the namespace attributes are extracted and stored.
		 * 
		 * @param node
		 *            to examine
		 * @param attributesOnly,
		 *            if true no recursion happens
		 */
		private void examineNode(Node node, boolean attributesOnly) {
			NamedNodeMap attributes = node.getAttributes();
			for (int i = 0; i < attributes.getLength(); i++) {
				Node attribute = attributes.item(i);
				storeAttribute((Attr) attribute);
			}

			if (!attributesOnly) {
				NodeList chields = node.getChildNodes();
				for (int i = 0; i < chields.getLength(); i++) {
					Node chield = chields.item(i);
					if (chield.getNodeType() == Node.ELEMENT_NODE)
						examineNode(chield, false);
				}
			}
		}

		/**
		 * This method looks at an attribute and stores it, if it is a namespace
		 * attribute.
		 * 
		 * @param attribute
		 *            to examine
		 */
		private void storeAttribute(Attr attribute) {
			// examine the attributes in namespace xmlns
			if (attribute.getNamespaceURI() != null
					&& attribute.getNamespaceURI().equals(
							XMLConstants.XMLNS_ATTRIBUTE_NS_URI)) {
				// Default namespace xmlns="uri goes here"
				if (attribute.getNodeName().equals(XMLConstants.XMLNS_ATTRIBUTE)) {
					putInCache(DEFAULT_NS, attribute.getNodeValue());
				} else {
					// Here are the defined prefixes stored
					putInCache(attribute.getLocalName(), attribute.getNodeValue());
				}
			}

		}

		private void putInCache(String prefix, String uri) {
			prefix2Uri.put(prefix, uri);
			uri2Prefix.put(uri, prefix);
		}

		/**
		 * This method is called by XPath. It returns the default namespace, if the
		 * prefix is null or "".
		 * 
		 * @param prefix
		 *            to search for
		 * @return uri
		 */
		public String getNamespaceURI(String prefix) {
			if (prefix == null || prefix.equals(XMLConstants.DEFAULT_NS_PREFIX)) {
				return prefix2Uri.get(DEFAULT_NS);
			} else {
				return prefix2Uri.get(prefix);
			}
		}

		/**
		 * This method is not needed in this context, but can be implemented in a
		 * similar way.
		 */
		public String getPrefix(String namespaceURI) {
			return uri2Prefix.get(namespaceURI);
		}

		public Iterator getPrefixes(String namespaceURI) {
			// Not implemented
			return null;
		}

	}
	
	public static String formats(String xml) {
		Document doc = null;
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

		try {
			DocumentBuilder docBuilder = dbf.newDocumentBuilder();
			try {
				doc = docBuilder.parse(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			} catch (SAXException | IOException e) {
				e.printStackTrace();
			}
			TransformerFactory tf = TransformerFactory.newInstance();
			Transformer transformer = tf.newTransformer();
			StreamResult xmlResult = new StreamResult(new ByteArrayOutputStream());
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.transform(new DOMSource(doc), xmlResult);
			return xmlResult.getOutputStream().toString();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerConfigurationException e) {
			e.printStackTrace();
		} catch (TransformerException e) {
			e.printStackTrace();
		}
		return xml;
	}
	
	/**
	 * 去除XML文本中的注释内容
	 *
	 * @param xmlContent XML文本
	 * @return 当传入为null时返回null
	 */
	public static String cleanComment(String xmlContent) {
		if (xmlContent == null) {
			return null;
		}
		return xmlContent.replaceAll("(?s)<!--.+?-->","");
	}

	public static void main(String[] args) {
		String a="<xml><appid><![CDATA[wx2787c3302c182d65]]><\\/appid>\\n<bank_type><![CDATA[OTHERS]]><\\/bank_type>\\n<cash_fee><![CDATA[1]]><\\/cash_fee>\\n<fee_type><![CDATA[CNY]]><\\/fee_type>\\n<is_subscribe><![CDATA[N]]><\\/is_subscribe>\\n<mch_id><![CDATA[1289729501]]><\\/mch_id>\\n<nonce_str><![CDATA[6a48147294474537b9cbfd819e81f0ac]]><\\/nonce_str>\\n<openid><![CDATA[oHStSsw7owoEDUrWloxO1mNW9bFc]]><\\/openid>\\n<out_trade_no><![CDATA[202403171943392121]]><\\/out_trade_no>\\n<result_code><![CDATA[SUCCESS]]><\\/result_code>\\n<return_code><![CDATA[SUCCESS]]><\\/return_code>\\n<sign><![CDATA[31D1B8E1BC9871A88F805F518FD0F03B]]><\\/sign>\\n<time_end><![CDATA[20240317194404]]><\\/time_end>\\n<total_fee>1<\\/total_fee>\\n<trade_type><![CDATA[NATIVE]]><\\/trade_type>\\n<transaction_id><![CDATA[4200002185202403175282346457]]><\\/transaction_id>\\n<\\/xml>";
		a=a.replace("\\/","/");
		System.out.println(xmlToMap(a));
	}
}
